package com.seres.gate.filter;

import com.alibaba.fastjson.JSONObject;
import com.seres.client.model.user.SysUser;
import com.seres.config.CacheInterface;
import com.seres.constant.CommonKey;
import com.seres.enums.ErrorEnum;
import com.seres.enums.OftenEnum;
import com.seres.enums.RedisCacheEnum;
import com.seres.gate.cache.DeptCache;
import com.seres.gate.eunm.FilterEnum;
import com.seres.oplogs.OplogService;
import com.seres.redis.RedisCache;
import com.seres.redis.RedisTableCache;
import com.seres.response.ApiResponse;
import com.seres.util.StreamTools;
import com.seres.util.StringUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.seres.util.TokenTools;
import com.seres.util.ValidatorTools;
import com.seres.vo.AuthUser;
import com.seres.vo.SysInterface;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Component
@Slf4j
public class AuthFilter extends ZuulFilter {

    @Autowired
    private CacheInterface cacheInterface;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private TokenTools tokenTools;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisTableCache redisTableCache;

    @Autowired
    private UserAuthService userAuthService;

    @Autowired
    private DeptCache deptCache;

    @Autowired
    private OplogService oplogService;


    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //获取当前请求上下文，在这个上下文基础上可以做很多事情了。具体自己查看API。
        RequestContext context = RequestContext.getCurrentContext();
        //获取原始Htpp请求，有这个对象也可以做很多事情了。
        HttpServletRequest request = context.getRequest();
        if (StringUtils.equals(request.getMethod(), HttpMethod.OPTIONS.name())) {
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(HttpStatus.OK.value());
            return null;
        }
        FilterEnum filter = urlFilter(request);
        if (filter.equals(FilterEnum.OPEN)) {
            return null;
        }
        String uri = getUri(request);
        SysInterface sysInterface = getUrlsMap().get(uri);
        if (sysInterface == null) {
            setUnauthorizedResponse(context, ErrorEnum.ACCESS_DENIED);
            return null;
        }
        if (sysInterface.getToken()) {
            return checkAuth(context, sysInterface);
        }
        return null;
    }

    public Object checkAuth(RequestContext context, SysInterface sysInterface) {
        String authToken = context.getRequest().getHeader(CommonKey.AUTHORIZATION);
        if (StringUtils.isEmpty(authToken)) {
            setUnauthorizedResponse(context, ErrorEnum.TOKEN_EXPIRED);
            return null;
        }
        if (StringUtils.startsWith(authToken, CommonKey.BEARER)) {
            authToken = StringUtils.replace(authToken, CommonKey.BEARER, "");
        }
        try {
            if (!this.tokenTools.validateToken(authToken)) {
                setUnauthorizedResponse(context, ErrorEnum.TOKEN_EXPIRED);
                return null;
            }
        } catch (Throwable e) {
            setUnauthorizedResponse(context, ErrorEnum.TOKEN_EXPIRED);
            log.error("无效的token:{},{}", authToken, e);
            return null;
        }
        AuthUser user = this.tokenTools.authUser(authToken);
        Long userId = user.getUserId();
        if (!checkOnlyOne(user, authToken)) {
            setUnauthorizedResponse(context, ErrorEnum.ONLY_ONE_DEVICES);
            return null;
        }
        SysUser sysUser = redisTableCache.get(SysUser.class, user.getUserId());
        if (sysUser == null) {
            setUnauthorizedResponse(context, ErrorEnum.USER_INFO_FAIL);
            return null;
        }
        context.addZuulRequestHeader(CommonKey.USER_ID, userId.toString());
        context.addZuulRequestHeader(CommonKey.USER_NAME, sysUser.getUserName());
        context.addZuulRequestHeader(CommonKey.DEPT_ID, sysUser.getDeptId().toString());
        context.addZuulRequestHeader(CommonKey.DEPT_IDS, getDataAuth(sysUser.getDeptId()));
        if (sysInterface.getAuth()) {
            List<String> auth = userAuthService.getMenuAndAuth(userId).getUrls();
            if (auth == null || auth.isEmpty() || !auth.contains(getUri(context.getRequest()))) {
                setUnauthorizedResponse(context, ErrorEnum.ACCESS_DENIED);
                return null;
            }
        }
        return null;
    }

    private boolean checkOnlyOne(AuthUser user, String authToken) {
        if (!user.isOnlyOne()) {
            return true;
        }
        //需要校验是否是单设备登录
        String tokenTemp = redisCache.getJedis().get(RedisCacheEnum.USER_TOKEN.createKey(user.getUserId()));
        //没有缓存，不处理接下来的逻辑
        if (StringUtils.isEmpty(tokenTemp)) {
            return true;
        }
        //缓存的token是否有效
        boolean isValidate = tokenTools.validateToken(tokenTemp);
        if (!isValidate) {
            //如果无效就不校验了
            return true;
        }
        //如果有效，判断一下两个token是否一样
        //两个token一样，说明是同一用户在访问
        return StringUtils.equals(authToken, tokenTemp);
        //有人用老的token在请求数据，打回去
    }

    public String getDataAuth(Long deptId) {
        List<Long> childes = new ArrayList<>();
        String res = "";
        if (!ValidatorTools.isSuper(deptId)) {
            childes = deptCache.getChildren(deptId);
            childes.add(deptId);
            if (childes.size() == 0) childes.add(Long.valueOf(OftenEnum.Super.DEFAULT.code()));
        }
        for (Long child : childes) {
            if (StringUtils.isBlank(res)) {
                res = res + child;
                continue;
            }
            res = res + "," + child;
        }
        return res;
    }

    private Map<String, SysInterface> getUrlsMap() {
        return StreamTools.toMap(cacheInterface.getUrlsByServers(discoveryClient.getServices()), SysInterface::getMethodPath);
    }


    private String getUri(HttpServletRequest request) {
        String uri = request.getRequestURI();
        return uri;
    }

    private FilterEnum urlFilter(HttpServletRequest request) {
        String uri = request.getRequestURI();
        if (StringUtils.indexOf(uri, CommonKey.SWAGGER_API) != -1) {
            return FilterEnum.OPEN;
        }
        //如果是swagger的uri,就默认开放
        if (StringUtils.indexOf(uri, CommonKey.SWAGGER) != -1) {
            return FilterEnum.OPEN;
        }
        if (StringUtils.indexOf(uri, CommonKey.FAVICON) != -1) {
            return FilterEnum.OPEN;
        }
        return FilterEnum.API;
    }

    /**
     * 设置 400 无权限状态
     */
    private void setUnauthorizedResponse(RequestContext requestContext, ErrorEnum errorEnum) {
        oplogService.createErrorLog(requestContext.getRequest(), errorEnum,"");
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
        requestContext.getResponse().setContentType("application/json;charset=UTF-8");
        requestContext.setResponseBody(JSONObject.toJSONString(ApiResponse.builder().code(errorEnum.getCode()).msg(errorEnum.getMsg()).build()));
    }
}
